package com.jade.journal.appender;

import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import com.jade.framework.base.util.DataUtils;
import com.jade.framework.base.util.SimpleDate;
import com.jade.journal.JournalEvent;
import com.jade.journal.JournalUtils;
import com.jade.journal.desc.AppenderDesc;
import com.jade.journal.desc.JournalDesc;

/**
 * 每日追加器。
 *
 * @author sky
 * @version 1.0 14-9-22 下午1:27
 */
public abstract class DailyAppender extends StreamAppender implements Runnable
{
    private static ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
    public static final int DEFAULT_TIMEOUT = 120000;

    /** 超时时间 */
    private int timeout = DEFAULT_TIMEOUT;

    /** 日志存放目录 */
    private String path;

    /** 当前日期 */
    private SimpleDate currentDate;

    /** 当前追加器 */
    private StreamAppender appender;

    /** 调度控制器 */
    private ScheduledFuture future;


    /** 追加器列表 */
    private final HashMap<SimpleDate, Cache> appenders;

    protected DailyAppender(String type, JournalDesc desc, AppenderDesc appenderDesc)
    {
        super(type, desc, appenderDesc);
        appenders = new HashMap<SimpleDate, Cache>();
    }

    public void init(JournalEvent event)
    {
        super.init(event);
        timeout = DataUtils.getInt(appenderDesc.getProperty("timeout"), DEFAULT_TIMEOUT);
        path = DataUtils.getString(appenderDesc.getProperty("path"), JournalUtils.getHome());
        future = monitor.scheduleAtFixedRate(this, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
    }

    public String getPath()
    {
        return path;
    }

    private StreamAppender createAppender(SimpleDate date, JournalEvent event)
    {
        String absolute = toAbsolute(date);
        StreamAppender appender = createAppender(date, absolute);
        appender.init(event);
        synchronized (appenders) {
            appenders.put(date, new Cache(appender, timeout));
        }
        return appender;
    }

    /**
     * 创建文件追加器
     *
     * @param absolute 绝对路径
     */
    protected abstract StreamAppender createAppender(SimpleDate date, String absolute);

    protected abstract String toAbsolute(SimpleDate date);

    public void doAppend(JournalEvent event)
    {
        //     SimpleDate date = new SimpleDate(event.getTime());
        SimpleDate date = event.getSimpleDate();
        doAppend(date, event);
    }

    private void doAppend(SimpleDate date, JournalEvent event)
    {
        int compare = date.compareTo(currentDate);
        StreamAppender appender;
        if (compare == 0) {
            appender = this.appender;
        }
        else {
            appender = getAppender(date, event);
            if (currentDate == null) {
                this.currentDate = date;
                this.appender = appender;
            }
        }

        if (compare > 0) {//当前日期超前
            synchronized (this) {
                if (date.compareTo(currentDate) > 0) {
                    //确认是否是今天
                    this.appender = appender;
                    this.currentDate = date;
                }
            }
        }

        appender.append((JournalEvent)event);
    }

    public void append(JournalEvent... events)
    {
        if (events == null || events.length == 0) {
            return;
        }
        for (JournalEvent event : events) {
            doAppend(event);
        }
    }

    /**
     * 返回追加器
     *
     * @param date 日期
     */
    private StreamAppender getAppender(SimpleDate date, JournalEvent event)
    {
        Cache cache = appenders.get(date);
        if (cache != null) {
            return cache.getAppender();
        }
        else {//Create it
            synchronized (appenders) {
                cache = appenders.get(date);
                if (cache == null) {
                    return createAppender(date, event);
                }
                return cache.getAppender();
            }
        }
    }

    /** 回收超时的appender */
    public void run()
    {
        synchronized (appenders) {
            if (log.isDebugEnabled()) {
                log.debug("Start to recyle appenders which are timeout....");
            }
            for (SimpleDate date : appenders.keySet()) {
                Cache cache = appenders.get(date);
                if (cache != null) {
                    if (log.isDebugEnabled()) {
                        log.debug(
                                "Check appender " + appender.getName() + ": timeout=" + cache.isTimeout(this.appender) +
                                " date:" + date);
                    }
                    if (cache.isTimeout(this.appender)) {
                        appenders.remove(date);
                        StreamAppender appender = cache.getAppender();
                        if (log.isDebugEnabled()) {
                            log.debug("Close appender " + appender.getName() + ":" + appender.getClass().getName());
                        }
                        appender.close();
                    }
                }
            }
        }
    }

    /** 关闭追加器 */
    public void close()
    {
        if (log.isDebugEnabled()) {
            log.debug("Close this daily appender!");
        }

        if (future != null) {
            future.cancel(true);
        }
        synchronized (appenders) {
            for (SimpleDate simpleDate : appenders.keySet()) {
                Cache cache = appenders.get(simpleDate);
                if (cache != null) {
                    StreamAppender appender = cache.getAppender();
                    appender.close();
                }
            }
            appenders.clear();
            appender = null;
        }
    }

    private static class Cache
    {
        private StreamAppender appender;
        private long time;
        private int timeout;

        public Cache(StreamAppender appender, int timeout)
        {
            this.appender = appender;
            this.timeout = timeout;
            access();
        }

        public boolean isTimeout(StreamAppender appender)
        {
            return this.appender != appender && System.currentTimeMillis() - time > timeout;
        }

        public void access()
        {
            this.time = System.currentTimeMillis();
        }

        public StreamAppender getAppender()
        {
            access();
            return appender;
        }

        StreamAppender getAppender0()
        {
            return appender;
        }
    }
}
